/* Printer status information */
/* Created 07.11.2004 T. Milius
   Changed 19.03.2005 T. Milius */
/* (c) Copyright 2004-2005 by Thomas Milius Stade, Germany
   Source must not be altered without agreement of the owner.
   The owner of the source is allowed to use this code inside programs without
   publishing the code of this programs. These programs may be commercial.
   Other developers can use this source freely inside own software if the source
   code of this programs is made public to same conditions like valid to this code
   and no commercial profit is taken from the programs based on this code

   Code or parts of it are not allowed to be used within GPL code or
   similar licenses which are "infecting" other code and trying to "supersede"
   other licenses. */
/** @file

    This file contains all structures and functions to handle DRAW files
    which can be plotted in a printer status window */
/* RISC OS */

#ifndef draw_h
#define draw_h

/* !!!!!!!!!! libraries !!!!!!!!!! */
/* ---------- ANSI-C ---------- */
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

/* ------------ Toolbox ------------ */
#include <wimplib.h>
#include <drawfile.h>

/* ------------ RISC OS Library ------------ */
#include <drawfobj.h>

/* ------------ own ------------ */
#include "variables.h"

/* !!!!!!!!!!! definitions !!!!!!!!!! */
/** @name DRAW object types

    This types will occur for draw objects */
/*@{*/
#define DRAW_OBJECT_TYPE_FONT_TABLE         0
#define DRAW_OBJECT_TYPE_TEXT               1
#define DRAW_OBJECT_TYPE_PATH               2
#define DRAW_OBJECT_TYPE_SPRITE             5
#define DRAW_OBJECT_TYPE_GROUP              6
#define DRAW_OBJECT_TYPE_TAGGED             7
#define DRAW_OBJECT_TYPE_TEXT_AREA          9
#define DRAW_OBJECT_TYPE_TEXT_COLUMN        10
#define DRAW_OBJECT_TYPE_OPTIONS            11
#define DRAW_OBJECT_TYPE_TRANSFORMED_TEXT   12
#define DRAW_OBJECT_TYPE_TRANSFORMED_SPRITE 13
/*@}*/

/** @name DRAW Scaling reference types

    This types will determine internally inside scan_draw_file()
    in which way DRAW objects are scaled. This means this types
    internally show whether objects scale to the center or right and left
    or up and down. They are determined from the name of the group object. */
/*@{*/
#define DRAW_SCALING_REFERENCE_NONE   0
#define DRAW_SCALING_REFERENCE_POSDIR 1
#define DRAW_SCALING_REFERENCE_NEGDIR 2
#define DRAW_SCALING_REFERENCE_MIDDLE 3
/*@}*/

/* !!!!!!!!!! data structures !!!!!!!!!! */
/** Contains a DRAW file and its transformation */
struct draw_file_struct {
draw_diag file;
Transform transformation;
};

/* !!!!!!!!!! support functions !!!!!!!!!! */
/**Determines the next draw_object inside a given DRAW file
    in accordance to a given draw_object and a given maximal
    size in which the next object may be located.

    Function is based on DRAW structures provided by
    RISC_OSLIb.

    A draw_object is a offset into a DRAW file. If
    no futher object is found inside the given limit
    a value is returned which indicates that the last object
    insid ethe given area has been reached. If no limit
    is given the size of the draw file is taken as limit.

    In general the next object is located immediately
    behind the actual object determined in dateil by the size
    of the actual object. In case that the actual object
    is located inside the DRAW file header the next object
    is located immendiately behind the DRAW file header. */
draw_object get_next_draw_object(draw_diag *diag,
                                 int sub_limit,
                                 draw_object actual_object)
{
int limit;
draw_objhdr *actual_object_header;

if (actual_object == draw_LastObject) return draw_LastObject;
if (sub_limit >= 0) {
  limit=sub_limit;
  }
else {
  limit=diag->length;
  }
if (actual_object < sizeof(draw_fileheader)) {
   if (sizeof(draw_fileheader) >= limit) return draw_LastObject;
   return sizeof(draw_fileheader);
   }
if (actual_object >= limit) return draw_LastObject;
actual_object_header=(draw_objhdr *)((unsigned long) diag->data + actual_object);
if ((actual_object + actual_object_header->size) >= limit) return draw_LastObject;
else return actual_object + actual_object_header->size;
}

/** Determines the type of a given draw_object inside
    a given DRAW file.

    Function is based on DRAW structures provided by
    RISC_OSLIb.

    If the object isn't located inside the given limits
    a value of -1 is retuned. */
int get_draw_object_type(draw_diag *diag,
                         int sub_limit,
                         draw_object actual_object)
{
int limit;
draw_objhdr *actual_object_header;

if (actual_object == draw_LastObject) return -1;
if (sub_limit >= 0) {
  limit=sub_limit;
  }
else {
  limit=diag->length;
  }
if (actual_object < sizeof(draw_fileheader)) {
  actual_object=sizeof(draw_fileheader);
  }
if (actual_object >= limit) return -1;
actual_object_header=(draw_objhdr *)((unsigned long) diag->data + actual_object);
return (int) actual_object_header->tag;
}

/** Returns the first object inside a given GROUP or
    TAGGED DRAW object of y given DRAW file. It also sets
    the limit information to the end of the given object.

    Function is based on DRAW structures provided by
    RISC_OSLIb. */
draw_object get_sub_draw_object(draw_diag *diag,
                                int sub_limit,
                                draw_object actual_object,
                                int *new_sub_limit)
{
int limit;
draw_objhdr *actual_object_header;

if (actual_object == draw_LastObject) {
  *new_sub_limit=-1;
  return draw_LastObject;
  }
if (sub_limit >= 0) {
  limit=sub_limit;
  }
else {
  limit=diag->length;
  }
if (actual_object < sizeof(draw_fileheader)) {
  actual_object=sizeof(draw_fileheader);
  }
if (actual_object >= limit) {
  *new_sub_limit=-1;
  return draw_LastObject;
  }
actual_object_header=(draw_objhdr *)((unsigned long) diag->data + actual_object);
switch(get_draw_object_type(diag,
                            sub_limit,
                            actual_object)) {
  case DRAW_OBJECT_TYPE_GROUP: {
    if (((int) actual_object + sizeof(draw_groustr)) >= limit) {
      *new_sub_limit=-1;
      return draw_LastObject;
      }
    *new_sub_limit=(int) actual_object + actual_object_header->size;
    return (draw_object) ((int) actual_object + sizeof(draw_groustr));
    }
  break;
  case DRAW_OBJECT_TYPE_TAGGED: {
    if (((int) actual_object + sizeof(draw_objhdr) + 4) >= limit) {
      *new_sub_limit=-1;
      return draw_LastObject;
      }
    actual_object_header=(draw_objhdr *)((unsigned long) diag->data + actual_object + sizeof(draw_objhdr) + 4);
    *new_sub_limit=(int) actual_object + sizeof(draw_objhdr) + 4 + actual_object_header->size;
    return (draw_object) ((int) actual_object + sizeof(draw_objhdr) + 4);
    }
  break;
  default: {
    *new_sub_limit=-1;
    return draw_LastObject;
    }
  }
}

/* !!!!!!!!!! functions !!!!!!!!!! */
/** Initializes a given draw_file_struct structure to no file */
bool initialize_draw_file(struct draw_file_struct *draw_file)
{

if (!draw_file) return false;
draw_file->file.data=NULL;
draw_file->transformation[0][0]=1<<16;
draw_file->transformation[0][1]=0;
draw_file->transformation[1][0]=0;
draw_file->transformation[1][1]=draw_file->transformation[0][0];
draw_file->transformation[2][0]=0;
draw_file->transformation[2][1]=0;
return true;
}

/** Removes a given DRAW-File from a draw_file_struct structure */
bool drop_draw_file(struct draw_file_struct *draw_file)
{

if (!draw_file) return false;
if (draw_file->file.data) {
  free(draw_file->file.data);
  }
return initialize_draw_file(draw_file);
}

/** Loads a given DRAW-File into draw_file_struct structure */
bool load_draw_file(struct draw_file_struct *draw_file,
                    char *file_name)
{
_kernel_swi_regs regs;

if (!draw_file) return false;
drop_draw_file(draw_file);
/* Determine filesize */
regs.r[0]=17;
regs.r[1]=(int) file_name;
if (_kernel_swi(OS_File, &regs, &regs)) {
  return false;
  }
if (regs.r[4] == 0) {
  return false;
  }
draw_file->file.length=regs.r[4];
if (!(draw_file->file.data=malloc(draw_file->file.length))) {
  drop_draw_file(draw_file);
  return false;
  }
/* load the draw file */
regs.r[0]=16;
regs.r[1]=(int) file_name;
regs.r[2]=(int) draw_file->file.data;
regs.r[3]=0;
if (_kernel_swi(OS_File, &regs, &regs) != 0) {
  drop_draw_file(draw_file);
  return false;
  }
else {
  return true;
  }
}

/** Parses a given DRAW file and modificates it in accordance
    to actual values of printer status variables.

    All DRAW elements on same level are parsed one by one.
    If text_value, scale_value, x_scaling and y_scaling
    are set to default nothing will be changed.
    If they are not having the default DRAW text will be
    replaced by the content of text_value and DRAW paths
    will be scaled down where value of 1024 relates
    to 100 percent.

    In case that there are tagged objects these are parsed
    be a recursive call of scan_draw_file() for all envclosed
    objects with the actual settings.

    The same happens for GROUP objects. However groups may be named.
    If a group starts with SC_ for scaling the scaling settings
    will be changed. Alternatively the name of the group may
    match a printer status variable in which case the text_value
    will be set to the content of the variable. Integer variables
    are converted into text. So all text elements of a group will
    be replaced by this text content. Please read the manual for
    a more detailed description of the
    <A HREF="../../../Help/1/Index.html#draw_files">group names</A>. */
bool scan_draw_file(draw_diag *diag,
                    int sub_limit,
                    draw_object actual_object,
                    struct variable_struct *general_variables,
                    char *text_value,
                    int scale_value,
                    int x_scaling,
                    int y_scaling)
{
int new_sub_limit;
draw_object sub_object;

while(actual_object != draw_LastObject) {
  switch(get_draw_object_type(diag,
                              sub_limit,
                              actual_object)) {
    case DRAW_OBJECT_TYPE_TEXT: {
      draw_textstr *actual_text_object;

      if (text_value) {
        actual_text_object=(draw_textstr *)((unsigned long) diag->data + actual_object);
        if ((actual_text_object->size - sizeof(draw_textstrhdr)) > 0) {
          actual_text_object->text[actual_text_object->size - sizeof(draw_textstrhdr) - 1]='\0';
          strncpy(actual_text_object->text, text_value, actual_text_object->size - sizeof(draw_textstrhdr) - 1);
          }
        }
      }
    break;
    case DRAW_OBJECT_TYPE_PATH: {
      draw_pathstrhdr *actual_path_object;
      draw_dashstrhdr *actual_dash_header;
      void *actual_element, *object_limit;
      int i;
      int number_of_points;
      int *actual_coordinate;

      if ((0 <= scale_value) &&
          (scale_value < 1024)) {
        actual_path_object=(draw_pathstrhdr *)((unsigned long) diag->data + actual_object);
        /* set to elements */
        actual_element=(void *)((unsigned long) diag->data + actual_object + sizeof(draw_pathstrhdr));
        /* Object limited by size */
        object_limit=(void *)((unsigned long) actual_element + actual_path_object->size);
        /* Skip dsh pattern */
        if ((actual_path_object->pathstyle.joincapwind & 0x80) != 0) {
          actual_dash_header=(draw_dashstrhdr *) actual_element;
          actual_element=(void *)((unsigned long) actual_element + sizeof(draw_dashstrhdr) + (actual_dash_header->dashcount*sizeof(int)));
          }
        /* parse all path elements */
        while(actual_element < object_limit) {
          switch(*((int *)actual_element)) {
            case 0: {
              number_of_points=0;
              actual_element=object_limit;
              }
            break;
            case 2: {
              number_of_points=1;
              }
            break;
            case 6: {
              number_of_points=3;
              }
            break;
            case 8: {
              number_of_points=1;
              }
            break;
            default: {
              number_of_points=0;
              }
            }
          actual_element=(void *)((unsigned long) actual_element + sizeof(int));
          actual_coordinate=(int *) actual_element;
          for (i=0; i < number_of_points; i++) {
            /* x component */
            switch(x_scaling) {
              case DRAW_SCALING_REFERENCE_POSDIR: {
                *actual_coordinate=((*actual_coordinate - actual_path_object->bbox.x1)*scale_value)/1024 + actual_path_object->bbox.x1;
                }
              break;
              case DRAW_SCALING_REFERENCE_NEGDIR: {
                *actual_coordinate=((*actual_coordinate - actual_path_object->bbox.x0)*scale_value)/1024 + actual_path_object->bbox.x0;
                }
              break;
              case DRAW_SCALING_REFERENCE_MIDDLE: {
                *actual_coordinate=((*actual_coordinate - ((actual_path_object->bbox.x1 + actual_path_object->bbox.x0)/2))*scale_value)/1024 + ((actual_path_object->bbox.x1 + actual_path_object->bbox.x0)/2);
                }
              break;
              }
            /* y component */
            actual_coordinate++;
            switch(y_scaling) {
              case DRAW_SCALING_REFERENCE_POSDIR: {
                *actual_coordinate=((*actual_coordinate - actual_path_object->bbox.y1)*scale_value)/1024 + actual_path_object->bbox.y1;
                }
              break;
              case DRAW_SCALING_REFERENCE_NEGDIR: {
                *actual_coordinate=((*actual_coordinate - actual_path_object->bbox.y0)*scale_value)/1024 + actual_path_object->bbox.y0;
                }
              break;
              case DRAW_SCALING_REFERENCE_MIDDLE: {
                *actual_coordinate=((*actual_coordinate - ((actual_path_object->bbox.y1 + actual_path_object->bbox.y0)/2))*scale_value)/1024 + ((actual_path_object->bbox.y1 + actual_path_object->bbox.y0)/2);
                }
              break;
              }
            /* next block */
            actual_coordinate++;
            }
          actual_element=(void *) actual_coordinate;
          }
        }
      }
    break;
    case DRAW_OBJECT_TYPE_GROUP: {
      char group_name[13];
      int i;
      void *general_variable;
      int type_of_variable;
      int new_x_scaling, new_y_scaling;
      int new_scale_value;
      int tag_length;
      draw_groustr *actual_group_object;

      sub_object=get_sub_draw_object(diag,
                                     sub_limit,
                                     actual_object,
                                     &new_sub_limit);
      actual_group_object=(draw_groustr *)((unsigned long) diag->data + actual_object);
      i=11;
      while((i >= 0) &&
            isspace(actual_group_object->name.ch[i])) i--;
      i++;
      strncpy(group_name, actual_group_object->name.ch, i);
      group_name[i]='\0';
      tag_length=strlen("SC_");
      if (strncmp(group_name, "SC_", tag_length) == 0) {
        if ((general_variable=get_address_of_variable(general_variables,
                                                      group_name + tag_length + 3,
                                                      &type_of_variable,
                                                      NULL)) != NULL) {
          if (type_of_variable == VARIABLE_TYPE_INTEGER) {
            new_scale_value=*((int *) general_variable);
            if ((new_scale_value >= 0) &&
                (new_scale_value < 1024)) {
              switch(group_name[tag_length]) {
                case 'M': {
                  new_x_scaling=DRAW_SCALING_REFERENCE_MIDDLE;
                  }
                break;
                case 'N': {
                  new_x_scaling=DRAW_SCALING_REFERENCE_NEGDIR;
                  }
                break;
                case 'P': {
                  new_x_scaling=DRAW_SCALING_REFERENCE_POSDIR;
                  }
                break;
                default: {
                  new_x_scaling=DRAW_SCALING_REFERENCE_NONE;
                  }
                }
              switch(group_name[tag_length + 1]) {
                case 'M': {
                  new_y_scaling=DRAW_SCALING_REFERENCE_MIDDLE;
                  }
                break;
                case 'N': {
                  new_y_scaling=DRAW_SCALING_REFERENCE_NEGDIR;
                  }
                break;
                case 'P': {
                  new_y_scaling=DRAW_SCALING_REFERENCE_POSDIR;
                  }
                break;
                default: {
                  new_y_scaling=DRAW_SCALING_REFERENCE_NONE;
                  }
                }
              scan_draw_file(diag,
                             new_sub_limit,
                             sub_object,
                             general_variables,
                             text_value,
                             new_scale_value,
                             new_x_scaling,
                             new_y_scaling);
              }
            else {
              scan_draw_file(diag,
                             new_sub_limit,
                             sub_object,
                             general_variables,
                             text_value,
                             scale_value,
                             x_scaling,
                             y_scaling);
              }
            }
          else {
            scan_draw_file(diag,
                           new_sub_limit,
                           sub_object,
                           general_variables,
                           text_value,
                           scale_value,
                           x_scaling,
                           y_scaling);
            }
          }
        else {
          scan_draw_file(diag,
                         new_sub_limit,
                         sub_object,
                         general_variables,
                         text_value,
                         scale_value,
                         x_scaling,
                         y_scaling);
          }
        }
      else if ((general_variable=get_address_of_variable(general_variables,
                                                         group_name,
                                                         &type_of_variable,
                                                         NULL)) != NULL) {
        switch(type_of_variable) {
          case VARIABLE_TYPE_CHAR: {
            scan_draw_file(diag,
                           new_sub_limit,
                           sub_object,
                           general_variables,
                           (char *) general_variable,
                           scale_value,
                           x_scaling,
                           y_scaling);
            }
          break;
          case VARIABLE_TYPE_INTEGER: {
            char general_variable_as_string[15];

            sprintf(general_variable_as_string,
                    "%d",
                    *((int *) general_variable));
            scan_draw_file(diag,
                           new_sub_limit,
                           sub_object,
                           general_variables,
                           general_variable_as_string,
                           scale_value,
                           x_scaling,
                           y_scaling);
            }
          break;
          default: {
            scan_draw_file(diag,
                           new_sub_limit,
                           sub_object,
                           general_variables,
                           text_value,
                           scale_value,
                           x_scaling,
                           y_scaling);
            }
          }
        }
      else {
        /* Nothing special but sub objects must be evaluated */
        scan_draw_file(diag,
                       new_sub_limit,
                       sub_object,
                       general_variables,
                       text_value,
                       scale_value,
                       x_scaling,
                       y_scaling);
        }
      }
    break;
    case DRAW_OBJECT_TYPE_TAGGED: {
      sub_object=get_sub_draw_object(diag,
                                     sub_limit,
                                     actual_object,
                                     &new_sub_limit);
      /* Nothing special but sub object must be evaluated */
      scan_draw_file(diag,
                     new_sub_limit,
                     sub_object,
                     general_variables,
                     text_value,
                     scale_value,
                     x_scaling,
                     y_scaling);
      }
    break;
    }
  actual_object=get_next_draw_object(diag,
                                     sub_limit,
                                     actual_object);
  }
return true;
}

/** Changes objects inside a DRAW file in accordance
    to actual values of printer status variables.

    It does so by calling the recursive function
    scan_draw_file() which does the real work with
    an initial setting. */
bool prepare_draw_file(struct draw_file_struct *draw_file,
                       struct variable_struct *general_variables)
{

scan_draw_file(&draw_file->file,
               -1,
               get_next_draw_object(&draw_file->file,
                                    -1,
                                    0),
               general_variables,
               NULL,
               1024,
               DRAW_SCALING_REFERENCE_NONE,
               DRAW_SCALING_REFERENCE_NONE);
return true;
}

#endif
